﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Objects;
using System.Data;

namespace IdeaNMR.Web.Core
{
    [Serializable]
    public abstract class BaseObjectFacade<T>
        where T : BaseObject, new()
    {
        protected IdeaNMRdbContainer Inmr
        {
            get
            {
                if (this._inmr == null)
                    this._inmr = new IdeaNMRdbContainer();
                return this._inmr;
            }
        }
        [NonSerialized]
        private IdeaNMRdbContainer _inmr;

        /// <summary>
        /// A current username
        /// </summary>
        protected string Username { get; private set; }

        protected User User
        {
            get
            {
                if (this._user == null)
                {
                    this._user = (from User u in this.Inmr.Users
                                  where u.Username == this.Username
                                  select u).FirstOrDefault();
                }
                if (this._user.EntityState == System.Data.EntityState.Detached)
                {
                    this.Inmr.Attach(this._user);
                }
                return this._user;
            }
        }
        [NonSerialized]
        private User _user;

        /// <summary>
        /// The real entity hidden from consumer.
        /// </summary>
        protected T Content
        {
            get
            {
                if (this._content != null && this._content.EntityState == System.Data.EntityState.Detached)
                    this.Inmr.Attach(this._content);
                return this._content;
            }
            set
            {
                this._content = value;
            }
        }
        private T _content;

        /// <summary>
        /// A copy of a real entity with only those relations, that are available to the current user.
        /// </summary>
        public T EntityObject
        {
            get
            {
                if (this._EntityObject == null)
                    this._EntityObject = this.GetContent();
                return this._EntityObject;
            }
            set
            {
                this._EntityObject = value;
            }
        }
        [NonSerialized]
        private T _EntityObject;

        /// <summary>
        /// Initializes BaseObjectFacade.
        /// </summary>
        /// <param name="username">Username of an author</param>
        public BaseObjectFacade(string username)
        {
            if (string.IsNullOrEmpty(username))
                throw new ArgumentException("BaseObjectFacade cannot be initialized with empty username.");

            this.Username = username;
        }

        protected abstract void LoadById(Guid TargetID);
        /// <summary>
        /// 
        /// </summary>
        /// <param name="VersionInfo"></param>
        /// <param name="mode"></param>
        /// <param name="IsNewVersion"></param>
        /// <returns></returns>
        public bool SaveChanges(string VersionInfo, SaveModeOptions mode, bool IsNewVersion)
        {
            T ho = null;
            StringBuilder WikiPageContent = new StringBuilder(@"(((<b style=""color: blue"">This page was created automatically for new component. You can use it to provide details relevant to the given component.</b>)))");

            if (mode == SaveModeOptions.Draft && this.Content != null && this.Content.ContentEquals(this.EntityObject)) return false;

            // If current content is draft already - reuse it.
            if (this.Content != null && this.Content.IsDraft)
                ho = this.Content;
            else if (this.Content != null)
            {
                // Try to find previous draft with current firstversionid
                ho = this.Inmr.BaseObjects.OfType<T>().Where(bb => bb.FirstVersion.Id == this.Content.FirstVersion.Id && bb.IsDraft).FirstOrDefault();
            }
            if (ho == null)
            {
                ho = new T();
                ho.Id = Guid.NewGuid();
                this.Inmr.AddToBaseObjects(ho);
            }

            ho.Datetime = DateTime.Now;

            this.EntityObject.CopyTo(ho, false);

            if (IsNewVersion)
            {
                BaseObject fvho = this.Content.FirstVersion;

                ho.FirstVersion = fvho;
                ho.VersionInfo = VersionInfo;
            }
            else
            {
                ho.FirstVersion = ho;
                ho.VersionInfo = "First version.";
                // Find wiki content related to the component that was used as a template for the current one.
                if (this.Content != null)
                {
                    EntityType eT = this.Content.FirstVersion.GetEntityType();
                    Guid eID = this.Content.FirstVersion.Id;
                    string pageName = string.Format("{0}_id_{1}", eT.ToString(), eID.ToString());
                    string pageContent = ScrewTurnWikiFacade.GetPageContent(pageName);
                    if (pageContent != null)
                    {
                        WikiPageContent = new StringBuilder();
                        WikiPageContent.AppendLine(@"(((<b style=""color: red"">This page was created automatically for new component. It might be criminally inaccurate. Please update the content to include data relevant to the current component.</b>)))").AppendLine(pageContent);
                    }
                }
            }

            switch (mode)
            {
                case SaveModeOptions.Draft:
                    ho.IsDraft = true;
                    ho.IsPrivate = true;
                    ho.VersionInfo = "Draft.";
                    break;
                case SaveModeOptions.Private:
                    ho.IsPrivate = true;
                    ho.IsDraft = false;
                    break;
                case SaveModeOptions.Public:
                    ho.IsPrivate = false;
                    ho.IsDraft = false;
                    // When the version becomes public
                    // if it is the first public version in a line
                    // it must become a first version of the line
                    // Example:
                    // ver1 has FirstVersion ver1 (itself) is private
                    // ver2 has FirstVersion ver1          is private
                    // ver3 has FirstVersion ver1          is private
                    // if you make ver4 public
                    // you must make FirstVersion of all ver1 ver2 ver3 and ver4 to be ver4
                    // and the Guid of ver4 will be in the name of the wiki page 
                    // (e.g. PulseProgram_id_{GuidOfVer4}.ashx)
                    bool createWikiPage = false;
                    if (ho.FirstVersion.IsPrivate)
                    {
                        var vers = ho.FirstVersion.DerivedVersions.ToArray();
                        ho.FirstVersion.FirstVersion = ho;
                        foreach (var v in vers)
                            v.FirstVersion = ho;
                        createWikiPage = true;
                    }
                    // The wiki page is created when:
                    // ho is the first version in a line (ho.FirstVersion is ho)
                    // or it is a firstpublic version (see before)
                    if (ho.FirstVersion == ho || createWikiPage)
                    {
                        EntityType eT = ho.GetEntityType();
                        string pageName = string.Format("{0}_id_{1}", eT.ToString(), ho.Id);
                        string pageContent = WikiPageContent.ToString();
                        string[] categories = { eT.ToCategoryName() };
                        ScrewTurnWikiFacade.CreatePage(pageName, eT.ToCategoryName() + " " + ho.Name, pageContent, categories);
                    }
                    break;
            }

            CreateRelationships(ho, mode);
            try
            {
                this.Inmr.SaveChanges();
            }
            catch (OptimisticConcurrencyException)
            {
                this.Inmr.Refresh(RefreshMode.ClientWins, ho);
                this.Inmr.SaveChanges();
            }

            this.Content = ho;
            this.EntityObject = null;

            return true;
        }

        /// <summary>
        /// Override this function to implement Obj user, Obj Authors and AuxComponents relations.
        /// </summary>
        /// <param name="obj"></param>
        /// <param name="smo"></param>
        protected abstract void CreateRelationships(T obj, SaveModeOptions smo);

        protected virtual T GetContent()
        {
            T result = new T();

            if (this.Content == null)
            {
                result.Datetime = DateTime.Now;
                result.FirstVersion = result;
                result.Name = result.Script = result.Info = string.Empty;
                result.VersionInfo = "This draft.";
            }
            else
            {
                this.Content.CopyTo(result, true);
            }

            return result;
        }

        /// <summary>
        /// Determine if current object has <paramref name="userName"/> as author.
        /// </summary>
        /// <param name="obj"></param>
        /// <returns>True if user is an author, false - otherwise.</returns>
        /// <remarks>
        /// For hardobject and NMRObject the Author to Object relations are different.
        /// Hence, this function must be overriden to implement this difference.
        /// </remarks>
        public abstract bool isUserAnAuthor(string userName);
    }

    public enum SaveModeOptions
    {
        Draft,
        Private,
        Public
    }
}
